package com.yanyeori.framework.core.util;

import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import ma.glasnost.orika.metadata.ClassMapBuilder;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * BeanUtil
 *
 * @author chenkuan
 * @since 2021/2/3
 */
public class BeanUtil {
    /**
     * 默认字段工厂
     */
    private static final MapperFactory MAPPER_FACTORY = new DefaultMapperFactory.Builder().build();

    /**
     * 默认字段实例
     */
    private static final MapperFacade MAPPER_FACADE = MAPPER_FACTORY.getMapperFacade();

    /**
     * 缓存实例集合
     */
    private static final Map<String, MapperFacade> CACHE_MAPPER = new ConcurrentHashMap<>();

    /**
     * @see #map(Object, Class)
     */
    public static <S, T> T copyProperties(S sourceEntity, Class<T> targetClass) {
        return map(sourceEntity, targetClass);
    }

    /**
     * @see #map(Object, Object)
     */
    public static <S, T> T copyProperties(S sourceEntity, T targetEntity) {
        return map(sourceEntity, targetEntity);
    }

    /**
     * @see #map(Object, Class, Map)
     */
    public static <S, T> T copyProperties(S sourceEntity, Class<T> targetClass, Map<String, String> refMap) {
        return map(sourceEntity, targetClass, refMap);
    }

    /**
     * @see #map(Object, Object, Map)
     */
    public static <S, T> T copyProperties(S sourceEntity, T targetEntity, Map<String, String> refMap) {
        return map(sourceEntity, targetEntity, refMap);
    }

    /**
     * @see #mapAsList(List, Class)
     */
    public static <S, T> List<T> copyToList(List<S> sourceEntityList, Class<T> targetClass) {
        return mapAsList(sourceEntityList, targetClass);
    }

    /**
     * @see #mapAsList(List, Class, Map)
     */
    public static <S, T> List<T> copyToList(List<S> sourceEntityList, Class<T> targetClass, Map<String, String> refMap) {
        return mapAsList(sourceEntityList, targetClass, refMap);
    }

    /**
     * 转换实体函数
     *
     * @param sourceEntity 源实体
     * @param targetClass  目标类对象
     * @param <S>          源泛型
     * @param <T>          目标泛型
     * @return 目标实体
     */
    public static <S, T> T map(S sourceEntity, Class<T> targetClass) {
        if (sourceEntity == null) return null;
        return MAPPER_FACADE.map(sourceEntity, targetClass);
    }

    /**
     * 转换实体函数
     *
     * @param sourceEntity 源实体
     * @param targetEntity 目标实体
     * @param <S>          源泛型
     * @param <T>          目标泛型
     * @return 目标实体
     */
    public static <S, T> T map(S sourceEntity, T targetEntity) {
        if (sourceEntity == null || targetEntity == null) return targetEntity;
        MAPPER_FACADE.map(sourceEntity, targetEntity);
        return targetEntity;
    }

    /**
     * 转换实体函数
     *
     * @param sourceEntity 源实体
     * @param targetClass  目标类对象
     * @param refMap       配置源类与目标类不同字段名映射
     * @param <S>          源泛型
     * @param <T>          目标泛型
     * @return 目标实体
     */
    public static <S, T> T map(S sourceEntity, Class<T> targetClass, Map<String, String> refMap) {
        if (sourceEntity == null) return null;
        return getMapperFacade(sourceEntity.getClass(), targetClass, refMap).map(sourceEntity, targetClass);
    }

    /**
     * 转换实体函数
     *
     * @param sourceEntity 源实体
     * @param targetEntity 目标实体
     * @param refMap       配置源类与目标类不同字段名映射
     * @param <S>          源泛型
     * @param <T>          目标泛型
     * @return 目标实体
     */
    public static <S, T> T map(S sourceEntity, T targetEntity, Map<String, String> refMap) {
        if (sourceEntity == null || targetEntity == null) return targetEntity;
        getMapperFacade(sourceEntity.getClass(), targetEntity.getClass(), refMap).map(sourceEntity, targetEntity);
        return targetEntity;
    }

    /**
     * 转换实体集合函数
     *
     * @param sourceEntityList 源实体集合
     * @param targetClass      目标类对象
     * @param <S>              源泛型
     * @param <T>              目标泛型
     * @return 目标实体集合
     */
    public static <S, T> List<T> mapAsList(List<S> sourceEntityList, Class<T> targetClass) {
        if (sourceEntityList == null) {
            return null;
        }
        if (sourceEntityList.size() == 0) {
            return new ArrayList<>(0);
        }
        return MAPPER_FACADE.mapAsList(sourceEntityList, targetClass);
    }

    /**
     * 转换实体集合函数
     *
     * @param sourceEntityList 源实体集合
     * @param targetClass      目标类对象
     * @param refMap           配置源类与目标类不同字段名映射
     * @param <S>              源泛型
     * @param <T>              目标泛型
     * @return 目标实体集合
     */
    public static <S, T> List<T> mapAsList(List<S> sourceEntityList, Class<T> targetClass, Map<String, String> refMap) {
        if (sourceEntityList == null) {
            return null;
        }
        if (sourceEntityList.size() == 0) {
            return new ArrayList<>(0);
        }
        return getMapperFacade(sourceEntityList.get(0).getClass(), targetClass, refMap).mapAsList(sourceEntityList, targetClass);
    }

    /**
     * 获取默认的MapperFacade
     * @return MapperFacade
     */
    public static MapperFacade getMapperFacade() {
        return MAPPER_FACADE;
    }

    /**
     * 获取自定义属性映射的MapperFacade
     *
     * @param source 原对象
     * @param target 目标对象
     * @return MapperFacade
     */
    public static <V, P> MapperFacade getMapperFacade(Class<V> source, Class<P> target, Map<String, String> refMap) {
        String key = source.getCanonicalName() + ":" + target.getCanonicalName();
        return CACHE_MAPPER.computeIfAbsent(key, k -> {
            DefaultMapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
            ClassMapBuilder<V, P> classMapBuilder = mapperFactory.classMap(source, target);
            if (MapUtils.isNotEmpty(refMap)) {
                refMap.forEach(classMapBuilder::field);
            }
            classMapBuilder.byDefault().register();
            return mapperFactory.getMapperFacade();
        });
    }

    /**
     * 设置方法为可访问（私有方法可以被外部调用）
     *
     * @param <T>              AccessibleObject的子类，比如Class、Method、Field等
     * @param accessibleObject 可设置访问权限的对象，比如Class、Method、Field等
     * @return 被设置可访问的对象
     */
    public static <T extends AccessibleObject> T setAccessible(T accessibleObject) {
        if (accessibleObject != null && !accessibleObject.isAccessible()) {
            accessibleObject.setAccessible(true);
        }
        return accessibleObject;
    }

    /**
     * 将Bean转换为【字段-值】的Map
     *
     * @param bean            bean
     * @param ignoreNullValue 忽略null值
     */
    public static <T> Map<String, Object> beanToMap(T bean, boolean ignoreNullValue) {
        return beanToMap(bean, ignoreNullValue, null);
    }

    /**
     * 将Bean转换为【字段-值】的Map
     *
     * @param bean            bean
     * @param ignoreNullValue 忽略null值
     * @param excludeFields   排除的字段
     */
    public static <T> Map<String, Object> beanToMap(T bean, boolean ignoreNullValue, List<String> excludeFields) {
        Map<String, Object> map = new HashMap<>();
        if (bean == null) {
            return map;
        }
        List<Field> fields = getClassFields(bean.getClass(), true);
        try {
            for (Field field : fields) {
                setAccessible(field);
                if (CollectionUtils.isNotEmpty(excludeFields) && excludeFields.contains(field.getName())) {
                    continue;
                }
                Object value = field.get(bean);
                if (ignoreNullValue && value == null) {
                    continue;
                }
                map.put(field.getName(), value);
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return map;
    }

    /**
     * 查找指定class及包含父类的所有字段属性
     *
     * @param clazz             指定class
     * @param includeSuperClass 是否包含父类字段
     */
    public static List<Field> getClassFields(Class<?> clazz, boolean includeSuperClass) {
        List<Field> fields = new ArrayList<>();
        Field[] tempFields;
        Class<?> superClazz;
        String objectName = Object.class.getSimpleName();
        do {
            tempFields = clazz.getDeclaredFields();
            if (tempFields.length > 0) {
                fields.addAll(new ArrayList<>(Arrays.asList(tempFields)));
            }
            if (!includeSuperClass) {
                break;
            }
            superClazz = clazz.getSuperclass();
            clazz = superClazz;
        } while (!objectName.equals(superClazz.getSimpleName()));
        return fields.stream().distinct().collect(Collectors.toList());
    }

    /**
     * 查找指定类中的指定name的字段（包括非public字段），也包括父类和Object类的字段， 字段不存在则返回{@code null}
     *
     * @param beanClass 被查找字段的类,不能为null
     * @param name      字段名
     * @return 字段
     * @throws SecurityException 安全异常
     */
    public static Field getField(Class<?> beanClass, String name) throws SecurityException {
        final List<Field> fields = getClassFields(beanClass, true);
        return fields.stream().filter(field -> name.equals(getFieldName(field))).findFirst().orElse(null);
    }

    /**
     * 获取字段名
     *
     * @param field 字段
     * @return 字段名
     */
    public static String getFieldName(Field field) {
        if (null == field) {
            return null;
        }
        return field.getName();
    }

    /**
     * 获得字段值，通过反射直接获得字段值，并不调用getXXX方法
     *
     * @param bean      对象
     * @param fieldName 字段名
     * @return 字段值
     */
    public static Object getFieldValue(Object bean, String fieldName) {
        if (null == bean || StringUtil.isEmpty(fieldName)) {
            return null;
        }
        return getFieldValue(bean, getField(bean instanceof Class ? (Class<?>) bean : bean.getClass(), fieldName));
    }

    /**
     * 获取字段值
     *
     * @param bean  对象，static字段则此字段为null
     * @param field 字段
     * @return 字段值
     */
    public static Object getFieldValue(Object bean, Field field) {
        if (field == null) {
            return null;
        }
        if (bean instanceof Class) {
            // 静态字段获取时对象为null
            bean = null;
        }

        setAccessible(field);
        Object result;
        try {
            result = field.get(bean);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return result;
    }
}
